package algo.math.evaluator.validator;
import java.util.ArrayList;
import java.util.List;

import algo.math.evaluator.base.exceptions.InvalidToken;
import algo.math.evaluator.context.EvaluationContext;
import algo.math.evaluator.context.Helper;
import algo.math.evaluator.context.operator.Operator;
import algo.math.evaluator.context.operator.interfaces.IOperator;
import algo.math.evaluator.exceptions.SyntaxException;
import algo.math.evaluator.exceptions.UnbalancedParenthesisException;
import algo.math.evaluator.exceptions.UnrecognizedVariableException;
import algo.math.evaluator.expression.Expression;
import algo.math.evaluator.tokenizer.interfaces.IToken;
import algo.math.evaluator.tokenizer.interfaces.IToken.TokenType;
import algo.math.evaluator.stack.LinkedStackSimple;
import algo.math.evaluator.stack.interfaces.ILifo;
import algo.math.evaluator.tokenizer.Token;

/**
 * An helper class for expression validation and formating before an evaluation 
 */
public class Validator {

	/**
	 * Validate an expression from the evaluation context.
	 *
	 */
	private void validateContextTokens (Expression expression) throws InvalidToken
	{
		for (String b : EvaluationContext.GetInstance().getBase().toStringArray()) {
			
			for (String v  : expression.getVariables().keySet()) {
				
				if (b.equals(v))
					throw new InvalidToken("Item "+ v +" is part of the cuurent avaluation number base.");
			}
		}
		
	}
	
	/**
	 * Validate the expression syntax.
	 *
	 */
	public void validate (Expression expression) throws Exception
    {
		
		validateContextTokens (expression);
		
        List<IToken> postfixTokens = infixToPostfix(expression).getTokens();
        ILifo<IToken> postfixStack = new LinkedStackSimple<IToken>();
        
        for (IToken token : expression.getTokens())
        {
            if (token.getType() == TokenType.CONSTANT) 
            {
                postfixTokens.add(token);
            }
            else if (token.getType() == TokenType.VARIABLE)
            {
            	if (expression.getVariables().containsKey(token.getValue()))
            	{
            		postfixTokens.add(token);
            	}
            	else
            	{
            		throw new UnrecognizedVariableException("Variable "+ token.getValue() + " not registered.");
            	}
            }
            else if (token.getType() == TokenType.FUNCTION)
            {
            	 postfixTokens.add(token);
            }
            else if (token.getType() == TokenType.OPERATOR)
            {
                if (Helper.getInstance().isOpenParenthesis(token))
                {
                    postfixStack.push(token);
                }
                else if (Helper.getInstance().isCloseParenthesis(token))
                {
                    while (true)
                    {
                        if (postfixStack.size() == 0)
                        {
                        	throw new UnbalancedParenthesisException("Missing righ parhenthesis.");
                        }

                        IToken top = null;
						top = postfixStack.pop();
                        
                        if (Helper.getInstance().isOpenParenthesis(top))
                        {
                            break;
                        }
                        else
                        {
                            postfixTokens.add(top);
                        }
                    }
                }
                else
                {
                	while (true)
                    {
                        if (postfixStack.size() == 0)
                        {
                            break;
                        }

                        IToken top = null;
						top = postfixStack.peek();
                        if (Helper.getInstance().isArithmeticOperator(top))
                        {
                                postfixStack.pop();
                                postfixTokens.add(top);
                               }
                        else
                        {
                            break;
                        }
                    }
                    postfixStack.push(token);
                }
            }
        }

        while (postfixStack.size() > 0)
        {
            IToken top = null;
			top = postfixStack.pop();
			
            if (Helper.getInstance().isOpenParenthesis(top) || Helper.getInstance().isCloseParenthesis(top))
            {
            	throw new UnbalancedParenthesisException("Missing righ parenthesis.");
            }
            else
            {
                postfixTokens.add(top);
            }
        }

        ILifo<IToken> evaluateStack = new LinkedStackSimple<IToken>();
        
        for (IToken token : postfixTokens)
        {
            if (token.getType() == TokenType.CONSTANT)
            {
                evaluateStack.push(token);
            }
            else if (token.getType() == TokenType.VARIABLE)
            {
                evaluateStack.push(token);
            }
            else if (token.getType() == TokenType.FUNCTION)
            {
                evaluateStack.push(token);
            }
            else if (token.getType() == TokenType.OPERATOR)
            {
                if (evaluateStack.size() >= 2)
                {
                    @SuppressWarnings("unused")
					IToken temp = evaluateStack.pop();
					@SuppressWarnings("unused")
					IToken top = evaluateStack.pop();
					
					evaluateStack.push(new Token ("1",TokenType.CONSTANT) );
                }
                else
                {
                	throw new SyntaxException("Missing Operand for Operator '" + token.getValue() +"' .");
                }
            }
        }

        if (evaluateStack.size() != 2) 
        {
        	throw new SyntaxException("Incomplete expression");
        }
    }
	

    /**
     * Format an expression prior to the evaluation.
     *
     */
    public Expression infixToPostfix(Expression inputExpression) throws Exception
    {
    	Expression postfixExpression = new Expression(null, null, null);
        List<IToken> postfixTokens = new ArrayList<IToken>();
        ILifo <IToken> postfixStack = new LinkedStackSimple <IToken>();
        
        for (IToken token : inputExpression.getTokens())
        {
            if (token.getType() == TokenType.CONSTANT) 
            {
                postfixTokens.add(token);
            }
            else if (token.getType() == TokenType.VARIABLE) 
            {
                postfixTokens.add(token);
            }
            else if (token.getType() == TokenType.FUNCTION)
            {
            	  postfixTokens.add(token);
            }
            else if (token.getType() == TokenType.OPERATOR) 
            {
                if (Helper.getInstance().isOpenParenthesis(token)) 
                {
                    postfixStack.push(token);
                }
                else if (Helper.getInstance().isCloseParenthesis(token)) 
                {
                    while (true)
                    {
                        if (postfixStack.size() == 0)
                        {
                        	throw new UnbalancedParenthesisException("Missing righ parhenthesis.");
                        }
                        IToken top = null;
						top = postfixStack.pop();
						
                        if (Helper.getInstance().isOpenParenthesis(top)) 
                        {
                        	break;
                        }
                        else
                        {
                        	postfixTokens.add(top);
                        }
                    }
                }
                else 
                {
                	Operator currentOperator = (Operator) EvaluationContext.GetInstance().getComponentById(IOperator.class, token.getValue()); //Helper.getInstance().findOperator((token.getValue()));
                    if (postfixStack.size() > 0)
                    {
                        IToken top= null;
						top = postfixStack.peek();
                        if (Helper.getInstance().isArithmeticOperator(top))
                        {
                            Operator stackOperator = (Operator) EvaluationContext.GetInstance().getComponentById(IOperator.class, top.getValue()); //Helper.getInstance().findOperator((token.getValue()));

                            if (
                                    currentOperator.getLevel() >= stackOperator.getLevel())
                               {
                            		postfixStack.pop();
                            		postfixTokens.add(top);
                               }
                        }
                    }

                    postfixStack.push(token); 
                }
            }
        }

        while (postfixStack.size() > 0)
        {
            IToken top = null;
			top = postfixStack.pop();
            if (Helper.getInstance().isOpenParenthesis(top) || Helper.getInstance().isCloseParenthesis(top))
            {
            	throw new UnbalancedParenthesisException("Wrong parenthesis.");
            }
            else
            {
                postfixTokens.add(top);
            }
        }

        postfixExpression = new Expression(postfixTokens,inputExpression.getVariables(),inputExpression.getBase());
        return postfixExpression;
    }

	
	
}
